// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2024.

//! Interrupt descriptor table management
//!
//! ## Handler Stubs
//!
//! It is tempting to populate all IDT entries with a pointer to the same shared interrupt service
//! routine. Unfortunately the x86 architecture does not define a mechanism for discovering the
//! current interrupt number. Software is expected to program each IDT entry with a different,
//! contextually appropriate ISR.
//!
//! Instead of writing 256 unique ISRs, this crate uses _build.rs_ to automatically generate 256
//! tiny "handler stubs". Each stub is a short assembly routine which pushes the current interrupt
//! number on the stack before jumping to the common ISR entrypoint in _handler_entry.rs_.
//!
//! The generated assembly also exports symbols for the first two stubs ([`handler_stub_0`] and
//! [`handler_stub_1`]). Care is taken to ensure each stub is the exact same size, which allows us
//! to use these symbols to compute the addresses of all the rest.

use crate::registers::dtables::{self, DescriptorTablePointer};
use crate::registers::irq::{BREAKPOINT_VECTOR, DEBUG_VECTOR, OVERFLOW_VECTOR};
use crate::registers::ring::Ring;
use crate::registers::segmentation::{
    BuildDescriptor, Descriptor, DescriptorBuilder, GateDescriptorBuilder,
};

use kernel::static_init;

use crate::segmentation::KERNEL_CODE;

use super::{NUM_VECTORS, SYSCALL_VECTOR};

extern "C" {
    /// First byte of handler stub 0
    static handler_stub_0: u8;

    /// First byte of handler stub 1
    static handler_stub_1: u8;
}

/// List of interrupt numbers which can be invoked from user mode.
///
/// When initializing the IDT, the interrupts listed here will have DPL set to 3. All other
/// interrupts will have DPL of 0.
const USER_ACCESSIBLE_INTERRUPTS: &[u8] = &[
    DEBUG_VECTOR,
    BREAKPOINT_VECTOR,
    OVERFLOW_VECTOR,
    SYSCALL_VECTOR,
];

/// Performs global initialization of interrupt handlers.
///
/// This function is primarily responsible for allocating and initializing the IDT. It creates an
/// entry for every possible interrupt vector (see [`NUM_VECTORS`]) referencing the corresponding
/// handler stub.
///
/// ## Safety
///
/// Must not be called more than once, or the IDT created by this function may become corrupted.
///
/// The kernel's segmentation must already be initialized (via [`segmentation::init`][crate::segmentation::init])
/// prior to calling this function, and it must never be changed afterwards.
///
/// After this function returns, it is safe to enable interrupts. However, interrupts below number
/// 32 must **never** be generated except by the CPU itself (i.e. exceptions), as doing so would
/// interfere with the internal handler stubs. This means that before enabling interrupts, the
/// caller must ensure that any hardware delivering external interrupts (such as the PIC/APIC) is
/// configured to use interrupt number 32 or above.
pub(super) unsafe fn init() {
    let idt = static_init!([Descriptor; NUM_VECTORS], [Descriptor::NULL; NUM_VECTORS]);

    // Handler stubs are laid out as an array in memory. We use the addresses of the first two stubs
    // to compute the size of each stub. This allows us to compute the address of any arbitrary stub
    // "i" using the expression "base + (size * i)".
    //
    // Safety: These symbols come from an assembly file which is generated by build.rs. We assume
    //         they have been generated correctly.
    let stub_base = unsafe { core::ptr::from_ref::<u8>(&handler_stub_0) as u32 };
    let stub_1_addr = unsafe { core::ptr::from_ref::<u8>(&handler_stub_1) as u32 };
    let stub_size = stub_1_addr - stub_base;

    for (i, idt_item) in idt.iter_mut().enumerate() {
        let stub_addr = stub_base + (stub_size * i as u32);

        // Certain interrupts need DPL of 3 so they can be invoked from user mode.
        let dpl = if USER_ACCESSIBLE_INTERRUPTS.contains(&(i as u8)) {
            Ring::Ring3
        } else {
            Ring::Ring0
        };

        let desc = DescriptorBuilder::interrupt_descriptor(KERNEL_CODE, stub_addr)
            .present()
            .dpl(dpl)
            .finish();
        *idt_item = desc;
    }

    unsafe {
        dtables::lidt(&DescriptorTablePointer::new_from_slice(idt));
    }
}
